﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Diagnostics;
using System.Threading;
using System.Xml;
using System.Linq;
using System.Xml.Serialization;
using System.IO;

namespace QuickTools.PluginUpdater
{
    public class UpdateDownloadFinishedEventArgs : EventArgs
    {
        public UpdateDownloadFinishedEventArgs(string file)
        {
            DownloadedFile = file;
        }
        public UpdateDownloadFinishedEventArgs(string file, Exception ex)
            : this(file)
        {
            Error = ex;
        }
        public string DownloadedFile { get; private set; }
        public Exception Error { get; private set; }
        public bool HasError
        {
            get
            { return Error != null; }
        }
    }

    public class Update
    {
        [XmlAttribute]
        public string Channel { get; set; }
        [XmlElement("Version")]
        public string VersionString
        {
            get
            {
                return Version.ToString();
            }
            set
            {
                Version = new Version(value);
            }
        }
        [XmlIgnore]
        public Version Version { get; set; }
        public string Url { get; set; }
        private string changelogUrl;

        public string ChangelogUrl
        {
            get { return changelogUrl; }
            set { changelogUrl = value; }
        }

        public event EventHandler<UpdateDownloadFinishedEventArgs> DownloadFinished;
        public event DownloadProgressChangedEventHandler ProgressChanged;

        public void DownloadAsync()
        {
            string fileName = Guid.NewGuid().ToString();
            string tmpFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(fileName, ".wireplugin"));
            WebClient w = new WebClient();
            w.DownloadFileCompleted += w_DownloadFileCompleted;
            DownloadProgressChangedEventHandler h = ProgressChanged;
            if (h != null)
            {
                w.DownloadProgressChanged += h;
            }
            w.DownloadFileAsync(new Uri(Url), tmpFile, new DownloadData { File = tmpFile, ProgressHandler = h, WebClient = w });
        }

        private class DownloadData
        {
            public string File { get; set; }
            public DownloadProgressChangedEventHandler ProgressHandler { get; set; }
            public WebClient WebClient { get; set; }
        }

        void w_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            DownloadData data = e.UserState as DownloadData;
            if (data.ProgressHandler != null)
                data.WebClient.DownloadProgressChanged -= data.ProgressHandler;
            data.WebClient.DownloadFileCompleted -= w_DownloadFileCompleted;
            data.WebClient.Dispose();

            EventHandler<UpdateDownloadFinishedEventArgs> h = DownloadFinished;
            if (h != null)
            {
                h(this, new UpdateDownloadFinishedEventArgs(data.File, e.Error));
            }
        }
    }

    public class UpdateCheckEventArgs : EventArgs
    {
        public UpdateCheckEventArgs(WirePluginUpdater.UpdateCheckParameters parameters, UpdateCollection updates)
        {
            Updates = updates;
            Parameters = parameters;
        }
        public UpdateCheckEventArgs(WirePluginUpdater.UpdateCheckParameters parameters, Exception ex)
        {
            Error = ex;
            Parameters = parameters;
        }
        public WirePluginUpdater.UpdateCheckParameters Parameters { get; set; }
        public UpdateCollection Updates { get; private set; }
        public Exception Error { get; private set; }
        public bool HasError { get { return Error != null; } }
    }

    [XmlRoot("UpdateInfo")]
    public class UpdateCollection
    {
        public string Product { get; set; }
        [XmlArrayItem("Channel")]
        public List<UpdateChannel> Channels { get; set; }
        public List<Update> Updates { get; set; }
    }

    public class UpdateChannel
    {
        public string Name { get; set; }
        public int Priority { get; set; }
    }
    
    public class WirePluginUpdater
    {
        public class UpdateCheckParameters
        {
            public bool HandleErrors { get; set; }
            public bool ShowMessageWhenNoUpdateAvailable { get; set; }
        }
        private string updateUrl;

        public string UpdateUrl
        {
            get { return updateUrl; }
            set { updateUrl = value; }
        }
        private string channel = "Stable";

        public string Channel
        {
            get { return channel; }
            set { channel = value; }
        }

        private Version currentVersion;

        public Version CurrentVersion
        {
            get { return currentVersion; }
            set { currentVersion = value; }
        }

        private UpdateCollection lastUpdateFile;

        public UpdateCollection LastUpdateFile
        {
            get { return lastUpdateFile; }
        }

        public Update GetPreferredUpdate(UpdateCollection f)
        {
            return GetPreferredUpdate(f, channel, currentVersion);
        }

        public static Update GetPreferredUpdate(UpdateCollection f, string channel, Version currentVersion)
        {
            Dictionary<string, UpdateChannel> channels = new Dictionary<string, UpdateChannel>();
            foreach (UpdateChannel c in f.Channels)
            {
                channels[c.Name] = c;
            }
            int wantedPriority = channels[channel].Priority;

            foreach (Update u in f.Updates)
            {
                if (!channels.ContainsKey(u.Channel))
                    continue;
                if (u.Version > currentVersion && channels[u.Channel].Priority >= wantedPriority)
                    return u;
            }
            return null;
        }


        public WirePluginUpdater(string updateUrl, Version currentVersion)
        {
            this.updateUrl = updateUrl;
            this.currentVersion = currentVersion;
        }
        
        public event EventHandler<UpdateCheckEventArgs> UpdateCheckComplete;
        private bool updateCheckRunning = false;
        private object updateLock = new object();
        private Thread updateCheckThread;

        public void CheckForUpdateAsync(bool handleErrors, bool showNoUpdateAvailableMessage)
        {
            lock (updateLock)
            {
                if (updateCheckRunning)
                    return;
                updateCheckRunning = true;
            }
            UpdateCheckParameters p = new UpdateCheckParameters { HandleErrors = handleErrors, ShowMessageWhenNoUpdateAvailable = showNoUpdateAvailableMessage };
            updateCheckThread = new Thread(new ParameterizedThreadStart(updateCheckAsync));
            updateCheckThread.Start(p);
        }

        private void updateCheckAsync(object parameters)
        {
            UpdateCheckParameters p = parameters as UpdateCheckParameters;

            EventHandler<UpdateCheckEventArgs> h = UpdateCheckComplete;
            if (h != null)
            {
                UpdateCheckEventArgs e;
                try
                {
                    UpdateCollection f = GetUpdateInfo(updateUrl);
                    e = new UpdateCheckEventArgs(p, f);
                }
                catch (Exception ex)
                {
                    e = new UpdateCheckEventArgs(p, ex);
                }
                h(this, e);
            }
            updateCheckRunning = false;
        }

        private static int CompareChannelPriority(UpdateChannel a, UpdateChannel b)
        {
            return b.Priority - a.Priority;
        }

        private static int CompareUpdates(Update a, Update b)
        {
            return b.Version.CompareTo(a.Version);
        }

        public static UpdateCollection GetUpdateInfo(string url)
        {
            XmlSerializer s = new XmlSerializer(typeof(UpdateCollection));
            XmlReader r = XmlReader.Create(url);
            UpdateCollection f = s.Deserialize(r) as UpdateCollection;
            f.Channels.Sort(CompareChannelPriority);
            f.Updates.Sort(CompareUpdates);
            return f;
        }
    }
}
